import React from 'react';
import {
  DragDropContext, Droppable, Draggable,
  DroppableProvided, DroppableStateSnapshot,
  DraggableProvided, DraggableStateSnapshot,
  DropResult,
} from 'react-beautiful-dnd';

import styles from './index.module.less';

interface IProp<T> {
  useDrag?: boolean; // 是否使用拖动功能，默认开启
  dataList: T[]; // 数据列表
  activeKey: string; // 当前选中项的 key 值
  keyFieldName?: string; // 用作 key 值的字段名，默认 id
  tabFieldName?: string; // 用作 tabName 值的字段名，默认 name
  stableKeys?: string[]; // 某些元素不参与拖动排序，默认显示在左侧
  extraContent?: JSX.Element; // 右侧自定义操作
  getTabContent?: (data: T) => JSX.Element; // 生成内容
  onMounted?: () => void; // 子组件获取数据有缺陷的 hack 方法
  onTabChange?: (key: string) => void; // tab 点击切换事件
  onDragEnd?: (dataList: T[]) => void; // 拖动结束事件
}

interface IState<T> {
  draggableDataList: T[];
  stableDataList: T[];
}

class DraggableTabs<T = any> extends React.Component<IProp<T>, IState<T>> {
  constructor(props: IProp<T>) {
    super(props);
    this.state = {
      draggableDataList: props.dataList.slice().filter(data => !props.stableKeys?.includes(data[props.keyFieldName ?? 'id'])),
      stableDataList: props.dataList.slice().filter(data => props.stableKeys?.includes(data[props.keyFieldName ?? 'id'])),
    };
  }

  componentDidMount(): void {
    this.props.onMounted?.();
    this.handleResetContentPosition();
  }

  componentDidUpdate(prevProps: Readonly<IProp<T>>, prevState: Readonly<IState<T>>, snapshot?: any): void {
    const { dataList, stableKeys, keyFieldName } = this.props;

    if (prevProps.dataList !== dataList) {
      this.setState({
        draggableDataList: dataList.slice().filter(data => !stableKeys?.includes(data[keyFieldName ?? 'id'])),
        stableDataList: dataList.slice().filter(data => stableKeys?.includes(data[keyFieldName ?? 'id'])),
      }, () => {
        this.props.onMounted?.();
        this.handleResetContentPosition();
      });
    }
  }

  handleResetContentPosition = () => {
    const { activeKey, keyFieldName } = this.props;
    const { draggableDataList, stableDataList } = this.state;
    let index = stableDataList.findIndex(data => data[keyFieldName] === activeKey);

    if (index !== -1) {
      this.handleSetMarginLeft(index);
    } else {
      index = draggableDataList.findIndex(data => data[keyFieldName] === activeKey);
      if (index !== -1) {
        this.handleSetMarginLeft(index + stableDataList.length);
      }
    }
  };

  /**
   * tab 点击事件，若有则调用回调函数
   * @param data 
   * @param index 
   */
  handleTabClick = (data: T, index: number) => {
    const { activeKey, keyFieldName } = this.props;
    if (activeKey === data[keyFieldName]) {
      return;
    }

    this.handleSetMarginLeft(index);
    this.props.onTabChange?.(data[this.props.keyFieldName]);
  };

  /**
   * 重置被选中标签的内容的位置
   * ! 注意: 可拖动元素的下标要加上前面不可拖动数组的长度
   * @param index 
   */
  handleSetMarginLeft = (index: number) => {
    const element = document.querySelector('#tabs-content-wrap') as HTMLDivElement;
    element.style.marginLeft = `-${index * 100}%`;
  };

  /**
   * 拖动结束
   * 顺序重排，若有则调用回调函数
   * @param result 
   * @returns 
   */
  handleDragEnd(result: DropResult) {
    if (!result?.destination) {
      return;
    }
    const list = this.reorder(
      this.state.draggableDataList,
      result.source.index,
      result.destination.index
    );
    this.props.onDragEnd?.(list);
    this.setState({
      draggableDataList: list,
    }, () => {
      // 如果当前选中的元素位置改变，其对应内容的位置也要重置
      this.handleResetContentPosition();
    });
  };

  /**
   * 数组排序，位置对调
   * @param list 
   * @param startIndex 
   * @param endIndex 
   * @returns 
   */
  reorder = (list: T[], startIndex: number, endIndex: number) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
  
    return result;
  };

  /**
   * 拖动-被悬浮元素的样式
   * @param isDraggingOver 
   * @returns 
   */
  getDragOverStyle = (isDraggingOver: boolean) => ({
    display: 'flex',
    background: isDraggingOver ? 'rgba(62, 91, 255, 0.1)' : '#fff',
  });
  /**
   * 拖动-被拖动元素的样式
   * @param isDragging 
   * @param draggableStyle 
   * @returns 
   */
  getDraggableStyle = (isDragging: boolean, draggableStyle: React.CSSProperties) => ({
    // some basic styles to make the items look a bit nicer
    userSelect: 'none',
    cursor: 'move',
    // change background colour if dragging
    background: isDragging ? 'rgba(62, 91, 255, 0.01)' : '#fff',
    // styles we need to apply on draggables
    ...draggableStyle,
  });

  render() {
    const {
      activeKey, keyFieldName = 'id', tabFieldName = 'name', extraContent = '',
      useDrag = true,
      getTabContent,
    } = this.props;
    const { draggableDataList, stableDataList } = this.state;

    // 栈溢出 - Draggable requires a draggableId
    if (
      (draggableDataList.length > 0 && !draggableDataList[0]?.[keyFieldName]) ||
      (stableDataList.length > 0 && !stableDataList[0]?.[keyFieldName])
    ) {
      return `${keyFieldName} 不存在`;
    }

    return (
      <div className={styles['draggable-tabs']} style={{ marginTop: '-16px' }}>
        <div className={styles['tabs-bar']}>
          <div className={styles['tab-list-wrap']}>
            <div className={styles['tab-list']}>
              {stableDataList.map((data, index) => (
                <div className={`${styles['tab-item']} ${styles['tab-item-stable']}
                    ${activeKey === data[keyFieldName] && styles['tab-item-active']}
                  `}
                  key={data[keyFieldName]}
                  onClick={() => this.handleTabClick(data, index)}
                >
                  {data[tabFieldName]}
                </div>
              ))}
              {!useDrag ? draggableDataList.map((data, index) => (
                <div className={`${styles['tab-item']} ${styles['tab-item-stable']}
                    ${activeKey === data[keyFieldName] && styles['tab-item-active']}
                  `}
                  key={data[keyFieldName]}
                  onClick={() => this.handleTabClick(data, index + stableDataList.length)}
                >
                  {data[tabFieldName]}
                </div>
              )) : (
                <DragDropContext onDragEnd={this.handleDragEnd.bind(this)}>
                  <Droppable droppableId='droppable' direction='horizontal'>
                    {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
                      <div
                        ref={provided.innerRef}
                        style={this.getDragOverStyle(snapshot.isDraggingOver)}
                        {...provided.droppableProps}
                      >
                        {draggableDataList.map((data, index) => (
                          <Draggable
                            key={data[keyFieldName]}
                            draggableId={data[keyFieldName]}
                            index={index}
                          >
                            {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
                              <div
                                ref={provided.innerRef}
                                {...provided.draggableProps}
                                {...provided.dragHandleProps}
                                style={this.getDraggableStyle(
                                  snapshot.isDragging,
                                  provided.draggableProps.style
                                )}
                              >
                                <div className={`${styles['tab-item']} ${styles['tab-item-stable']}
                                    ${activeKey === data[keyFieldName] && styles['tab-item-active']}
                                  `}
                                  key={data[keyFieldName]}
                                  onClick={() => this.handleTabClick(data, index + stableDataList.length)}
                                >
                                  {data[tabFieldName]}
                                </div>
                              </div>
                            )}
                          </Draggable>
                        ))}
                        {provided.placeholder}
                      </div>
                    )}
                  </Droppable>
                </DragDropContext>
              )}
            </div>
            <div className={styles['tabs-ink-bar']}></div>
          </div>
          <div className={styles['extra-content']}>
            {extraContent}
          </div>
        </div>
        <div id='tabs-content-wrap' className={styles['tabs-content-wrap']}>
          {[...stableDataList, ...draggableDataList].map(data => (
            <div className={`
                ${styles['tab-content']}
                ${activeKey === data[keyFieldName] ? styles['tab-content-active'] : styles['tab-content-deactive']}
              `}
              key={data[keyFieldName]}
            >
              {getTabContent?.(data)}
            </div>
          ))}
        </div>
      </div>
    );
  }
}

export default DraggableTabs;
